Pressidian
花园入口
笔记
项目
关于
实验室
GitHub
花园入口
笔记
项目
关于
实验室
GitHub

KNOWLEDGE PATHS

笔记库
当前位置
笔记库/前端/三件套/JS/JavaScript

12 二进制与文件

14 分钟阅读 · Note

目录树 578 篇

            • 00 JavaScript学习地图
            • 01 JavaScript基础
            • 02 类型系统与类型转换
            • 03 Number与String
            • 04 Array数组
            • 05 Object对象
            • 06 集合与迭代
            • 07 函数、作用域与this
            • 08 原型、构造函数与类
            • 09 Generator与异步迭代
            • 10 异步编程
            • 11 浏览器与DOM
            • 12 二进制与文件
            • 13 标准内建对象
            • 14 模块化
            • 15 错误处理
            • 16 JSDoc
            • 90 JavaScript知识点总目录
            • 91 JavaScript修正与补充记录
          • 前端模块化
      • 前端技术栈
    • 笔记目录
    • CLAUDE.md
    • Vue 组件与 Render 函数

关联笔记 6

↗00 JavaScript学习地图反向链接↗90 JavaScript知识点总目录反向链接↗91 JavaScript修正与补充记录反向链接↗01 JavaScript基础同一路径↗02 类型系统与类型转换同一路径↗03 Number与String同一路径
  • 12 二进制与文件

12 二进制与文件

> Last Format Time:8/11/2026 21:45:40

本笔记由两个 JavaScript 教程目录中的同主题内容合并而成;全部旧知识保留在“旧笔记知识全集(按来源保留)”中,并由导航逐项索引。


当前前端开发关键要点

> [!important] 学习优先级:P1 > - 区分 ArrayBuffer、TypedArray、DataView、Blob、File 和 Object URL 的职责。 > - 上传、预览和解析文件时校验类型与大小,及时调用 URL.revokeObjectURL 释放资源。 > - 大文件处理优先采用流式、分片或 Worker 方案,避免阻塞主线程和制造高内存峰值。 > - MIME 与扩展名都不能作为绝对安全依据,服务端仍需重新校验。


知识点导航

[现代JS教程与阮一峰ES6 · 5特殊内建对象/二进制与文件读取/二进制](./12 二进制与文件.md)

  • ArrayBuffer、TypedArray 与 DataView 详解
  • ArrayBuffer - 原始二进制数据缓冲区
  • TypedArray - 类型化数组视图
  • TypedArray 的特点
  • 常见的 TypedArray 类型
  • 基本创建与使用
  • 直接初始化
  • TypedArray 与普通数组的核心区别
  • DataView - 灵活的数据访问视图
  • 内存布局与字节序
  • ==实际应用示例==
  • 性能优化与使用场景
  • 与 Blob 的转换
  • Blob、File 与 Object URL 详解
  • Blob 对象
  • File 对象
  • FileReader 读取文件内容
  • Object URL
  • Base64 与 DataURL
  • Data URL
  • 关系
  • 内存管理最佳实践
  • 使用场景对比
  • MIME
  • 类型(Type)
  • 子类型(Subtype)
  • 常见 MIME 类型示例
  • 重要参数:charset
  • 在实际中的应用

[现代JS教程与阮一峰ES6 · 5特殊内建对象/二进制与文件读取/最佳实践](./12 二进制与文件.md)

  • 正文主题:最佳实践

[廖雪峰教程 · 7浏览器/7.3 文件](./12 二进制与文件.md)

  • 文件
  • File API
  • 回调

现代前端补充与纠错

> [!info] 修改标记 > - 【修正】:旧教程中错误、过时或容易误导的内容。 > - 【补充】:旧教程未覆盖、但当前前端开发需要掌握的内容。 > - 【修正代码】:替换或校正了旧代码示例。

> [!warning] 下方保留旧笔记;二进制处理必须同时考虑字节视图、资源释放、流式处理和安全边界。

数据视图

  • 【补充】 ArrayBuffer 表示固定长度原始字节,TypedArray 提供同构数值视图,DataView 适合混合类型和显式大小端解析。
  • 【补充】 切片、视图和复制并不总是同义;先确认新对象是否共享底层 buffer。
  • 【补充】 跨 Worker 传输大 buffer 可使用 transferable 转移所有权,避免复制;转移后原 buffer 会被分离。

文件与 Blob

  • 【补充】 File 是带名称、类型、时间等元数据的 Blob;客户端给出的 MIME、扩展名和文件名都不可信,上传后仍需服务端校验。
  • 【补充】 URL.createObjectURL 创建的对象 URL 不再使用时应调用 URL.revokeObjectURL,避免资源长期占用。
  • 【补充】 小文件可使用 arrayBuffer()/text();大文件优先考虑 Streams、分片、背压、进度与取消,不要一次性全部读入内存。
  • 【补充】 Base64 会增加体积,适合文本通道兼容,不等于加密或安全编码。

旧笔记知识全集(按来源保留)

现代JS教程与阮一峰ES6 · 5特殊内建对象/二进制与文件读取/二进制

ArrayBuffer、TypedArray 与 DataView 详解
ArrayBuffer - 原始二进制数据缓冲区

[补充说明]:ArrayBuffer 表示通用的、固定长度的原始二进制数据缓冲区。

// 创建 ArrayBuffer [修正术语:Arraybuffer → ArrayBuffer]
const buffer = new ArrayBuffer(16); // 创建16字节的缓冲区
console.log(buffer.byteLength);    // 16

// 不能直接操作 ArrayBuffer,需要通过视图操作
// console.log(buffer[0]); // 错误:不能直接访问
TypedArray - 类型化数组视图

[补充说明]:TypedArray 提供了对 ArrayBuffer 的类型化视图访问。 在 JavaScript 中,TypedArray(类型化数组) 是一组用于处理二进制数据的数组-like对象,专为高效操作原始二进制数据而设计。它们与普通数组(Array)的主要区别在于:存储的数据类型固定(如整数、浮点数等),且长度不可变,因此性能更高,尤其适合处理大量二进制数据(如音频、图像、网络协议数据等)。

TypedArray 的特点
  1. 固定数据类型:每个元素的类型在创建时确定(如 8位整数、32位浮点数等),避免了普通数组的动态类型转换开销。
  2. 固定长度:创建时需指定长度,创建后无法通过 push/pop 等方法改变(类似 C 语言的数组)。
  3. 存储原始二进制数据:直接映射到内存中的二进制数据缓冲区(ArrayBuffer),读写效率远高于普通数组。
  4. 不继承 Array.prototype:不支持 Array 的部分方法(如 concat、splice),但支持索引访问和 length 属性。
常见的 TypedArray 类型

JavaScript 提供了多种 TypedArray 构造函数,对应不同的数据类型和字节长度:

构造函数元素类型字节长度取值范围(示例)
Int8Array8位有符号整数1-128 到 127
Uint8Array8位无符号整数10 到 255
Uint8ClampedArray8位无符号整数(截断)1超出范围时自动截断为 0 或 255
Int16Array16位有符号整数2-32768 到 32767
Uint16Array16位无符号整数20 到 65535
Int32Array32位有符号整数4-2³¹ 到 2³¹-1
Uint32Array32位无符号整数40 到 2³²-1
Float32Array32位浮点数(单精度)4约 ±3.4×10³⁸
Float64Array64位浮点数(双精度)8约 ±1.8×10³⁰⁸

TypedArray 的使用示例 TypedArray 必须基于 ArrayBuffer(原始二进制数据缓冲区)创建,直接创建会在内部自动创建,ArrayBuffer 是一块原始的内存区域,TypedArray 则是对这块内存的“视图”(按指定类型解析数据)。

基本创建与使用
// 1. 创建一个 16 字节的 ArrayBuffer(内存缓冲区)
const buffer = new ArrayBuffer(16);

// 2. 创建 TypedArray 视图(以 32位整数解析 buffer)
const int32View = new Int32Array(buffer);

// 3. 长度由缓冲区大小和元素字节长度决定:16字节 / 4字节/元素 = 4个元素
console.log(int32View.length); // 4

// 4. 操作元素(索引访问)
int32View[0] = 100;
int32View[1] = 200;
console.log(int32View); // Int32Array(4) [100, 200, 0, 0]
// 查看对应的原始二进制数据缓冲区
// ArrayBuffer {
//   [Uint8Contents]: <64 00 00 00 c8 00 00 00 00 00 00 00 00 00 00 00>,
//   byteLength: 16
// }

// 5. 不同视图解析同一块缓冲区(二进制数据的灵活解读)
const uint8View = new Uint8Array(buffer);
console.log(uint8View); // Uint8Array(16) [100, 0, 0, 0, 200, 0, 0, 0, 0, ...]

64 00 00 00 c8 00 00 00 00 00 00 00 00 00 00 00 低位—————————————————高位 对应16个字节,int32是用32个位,就是4个字节,也就是四个一组,转换为10进制: 对应100,200,0,0

直接初始化

也可以直接通过长度或数组初始化 TypedArray(内部会对应的==自动创建== ==ArrayBuffer==):

// 用长度初始化(8个 16位整数)
const int16Arr = new Int16Array(8)
int16Arr[0] = 32767 // 最大16位有符号整数
console.log(int16Arr.buffer);
// ArrayBuffer {
//   [Uint8Contents]: <ff 7f 00 00 00 00 00 00 00 00 00 00 00 00 00 00>,
//   byteLength: 16
// }

// 用数组初始化
const float64Arr = new Float64Array([1.5, 2.5, 3.5])
console.log(float64Arr[1]) // 2.5
console.log(float64Arr.buffer)
// ArrayBuffer {
//   [Uint8Contents]: <00 00 00 00 00 00 f8 3f 00 00 00 00 00 00 04 40 00 00 00 00 00 00 0c 40>,
//   byteLength: 24
// }
TypedArray 与普通数组的核心区别
特性TypedArray普通数组(Array)
元素类型固定(如 Int32、Float64)动态(可混合各种类型)
长度固定(创建后不可变)可变(可通过 push 等方法修改)
存储方式二进制缓冲区(高效)动态对象存储(有额外开销)
继承关系不继承 Array.prototype继承 Array.prototype
适用场景二进制数据处理(音视频等)通用数据存储与操作
// 不同类型的 TypedArray 视图
const int8Array = new Int8Array(buffer);      // 8位有符号整数
const uint8Array = new Uint8Array(buffer);    // 8位无符号整数
const int16Array = new Int16Array(buffer);    // 16位有符号整数
const uint16Array = new Uint16Array(buffer);  // 16位无符号整数
const int32Array = new Int32Array(buffer);    // 32位有符号整数
const uint32Array = new Uint32Array(buffer);  // 32位无符号整数
const float32Array = new Float32Array(buffer); // 32位浮点数
const float64Array = new Float64Array(buffer); // 64位浮点数

// 直接创建带数据的 TypedArray
const dataArray = new Uint8Array([1, 2, 3, 4, 5]);
console.log(dataArray.length);    // 5
console.log(dataArray.byteLength); // 5
DataView - 灵活的数据访问视图

[补充说明]:DataView 提供了更灵活的低级接口来读写 ArrayBuffer 中的数据。

// 创建 DataView
const buffer = new ArrayBuffer(4)
const view = new DataView(buffer)


// 设置和读取数据(可以指定字节偏移和端序)
view.setInt8(0, 127) // 在位置0写入8位有符号整数
console.log(view.buffer)
// ArrayBuffer { [Uint8Contents]: <7f 00 00 00>, byteLength: 4 }

// 小端序写入16位无符号整数
view.setUint16(2, 65534, true)
console.log(view.buffer);
// ArrayBuffer { [Uint8Contents]: <7f 00 fe ff>, byteLength: 4 }


console.log(view.getInt8(0))
// 127 因为就一位所以不分高低了

console.log(view.getUint16(2, true))
// 65534 (小端序读取)从低位(fe)到高位(ff)

console.log(view.getUint16(2))
// 65279 (大端序读取)反之

内存布局与字节序

[补充说明]:理解内存布局对于二进制数据处理至关重要。

// 演示字节序(Endianness)的影响
const testBuffer = new ArrayBuffer(4);
const view1 = new DataView(testBuffer);

// 写入32位整数
view1.setInt32(0, 0x12345678, false); // 大端序
console.log(new Uint8Array(testBuffer)); // [0x12, 0x34, 0x56, 0x78]

view1.setInt32(0, 0x12345678, true);  // 小端序
console.log(new Uint8Array(testBuffer)); // [0x78, 0x56, 0x34, 0x12]
==实际应用示例==
// 示例:处理二进制文件格式(如图像文件头)
function parsePNGHeader(buffer) {
    const view = new DataView(buffer);

    // 检查PNG文件签名
    const signature = view.getUint32(0, false);
    if (signature !== 0x89504E47) {
        throw new Error('不是有效的PNG文件');
    }

    // 读取图像尺寸
    const width = view.getUint32(16, false);
    const height = view.getUint32(20, false);

    return { width, height };
}

// 示例:创建和操作二进制数据
function createBinaryData() {
    const buffer = new ArrayBuffer(12);
    const view = new DataView(buffer);

    // 写入不同类型的数据
    view.setFloat32(0, Math.PI, true);    // 32位浮点数(小端序)
    view.setUint16(4, 1000, false);       // 16位无符号整数(大端序)
    view.setInt8(6, -50);                 // 8位有符号整数

    return buffer;
}
性能优化与使用场景
数据类型适用场景优点缺点
ArrayBuffer原始二进制数据存储内存高效不能直接操作
TypedArray类型化数据处理高性能,类型安全固定数据类型
DataView混合数据类型访问灵活,支持端序控制性能稍低
// 性能比较:TypedArray vs DataView
const testData = new ArrayBuffer(1000000);

// 使用 TypedArray(更快)
console.time('TypedArray');
const uint8View = new Uint8Array(testData);
for (let i = 0; i < uint8View.length; i++) {
    uint8View[i] = i % 256;
}
console.timeEnd('TypedArray');

// 使用 DataView(更灵活但稍慢)
console.time('DataView');
const dataView = new DataView(testData);
for (let i = 0; i < testData.byteLength; i++) {
    dataView.setUint8(i, i % 256);
}
console.timeEnd('DataView');
与 Blob 的转换
// ArrayBuffer 转 Blob
const arrayBuffer = new ArrayBuffer(1024);
const blob = new Blob([arrayBuffer], {type: 'application/octet-stream'});

// Blob 转 ArrayBuffer
async function blobToArrayBuffer(blob) {
    return await blob.arrayBuffer(); // 现代浏览器支持的方法
    // 或使用 FileReader
    // return new Promise((resolve) => {
    //     const reader = new FileReader();
    //     reader.onloadend = () => resolve(reader.result);
    //     reader.readAsArrayBuffer(blob);
    // });
}
Blob、File 与 Object URL 详解
Blob 对象

[补充说明]:Blob(Binary Large Object)表示不可变的原始数据类文件对象。

// 创建 Blob 对象
let debug = { name: 'Dano' }
let str = JSON.stringify(debug)
console.log(str)
// {"name":"Dano"} 15个字符

// 语法:new Blob(array, options)
let blob = new Blob([str], { type: 'application/json' })

// 获取 Blob 信息
console.log(blob) // Blob { size: 15, type: 'application/json' }
console.log(blob.size) // Blob 大小(字节)15
console.log(blob.type) // MIME 类型 application/json

在JavaScript中,字符的存储方式取决于字符串的编码类型。JavaScript字符串使用UTF-16编码,这意味着每个字符通常使用==2个字节==(16位)存储。但是,对于Unicode中超出基本多文种平面(BMP)的字符(即码点大于0xFFFF的字符),它们需要使用两个16位码元(即==4个字节==)来表示,这称为代理对(surrogate pair)。

因此,一般情况下,一个字符占用2个字节,但对于辅助平面中的字符,则占用4个字节。

// 创建 Blob 对象
let debug = { name: 'Dano😊' }
let str = JSON.stringify(debug)
console.log(str)

let blob = new Blob([str], { type: 'application/json' })

// 获取 Blob 信息
console.log(blob)
console.log(blob.size)
console.log(blob.type)
// {"name":"Dano😊"}
// Blob { size: 19, type: 'application/json' }
// 19
// application/json
File 对象

[补充说明]:File 对象继承自 Blob,表示文件数据。

// 通过 input 获取 File 对象
const fileInput = document.getElementById('fileInput');
fileInput.addEventListener('change', (event) => {
    const file = event.target.files[0];
    console.log(file.name);    // 文件名
    console.log(file.size);    // 文件大小
    console.log(file.type);    // 文件类型
    console.log(file.lastModified); // 最后修改时间
});

// 创建 File 对象
const file = new File([JSON.stringify(debug)], 'user.json', {
    type: 'application/json',
    lastModified: Date.now()
});
FileReader 读取文件内容

[修正代码]:FileReader是浏览器独有的API在Node中

let json = { name: 'Dano😊' }

const file = new File([JSON.stringify(json)], 'user.json', {
    type: 'application/json',
    lastModified: Date.now(),
})

console.log(file)
// File {
//   size: 19,
//   type: 'application/json',
//   name: 'user.json',
//   lastModified: 1757595534532
// }

let reader = new FileReader()
reader.readAsArrayBuffer(file)

reader.onload = function (e) {
    const arrayBuffer = e.target.result
    // 将 ArrayBuffer 转换为字符串
    const decoder = new TextDecoder('utf-8')
    const jsonString = decoder.decode(arrayBuffer)
    const parsedJson = JSON.parse(jsonString)

    // 显示结果
    console.log(file, arrayBuffer, parsedJson)
}

reader.onerror = function (e) {
    console.error('读取文件时出错: ', e.target.error)
}

![Pasted image 20250911211549](../../../../_assets/6dacdad-Pasted image 20250911211549.png)

Object URL

[补充说明]:创建指向Blob/File对象的临时URL

function download() {
    let debug = {name: 'Domo'};
    let blob = new Blob([JSON.stringify(debug)], {type: 'application/json'});

    // 创建下载链接
    let a = document.createElement('a');
    a.download = 'user.json';
    a.rel = 'noopener'; // 安全优化
    a.href = URL.createObjectURL(blob);

    // 模拟点击下载
    a.click();

    // 释放URL资源 [补充说明]
    URL.revokeObjectURL(a.href);
}
Base64 与 DataURL

[补充说明]:

// Data URL 格式:data:[<mediatype>][;base64],<data>
const dataURL = 'data:application/json;base64,eyJuYW1lIjoiRG9tbyJ9';

// Base64 编码解码
const base64Encoded = btoa('Hello World'); // 编码
const base64Decoded = atob('SGVsbG8gV29ybGQ='); // 解码

// Blob 转 Base64
function blobToBase64(blob) {
    return new Promise((resolve) => {
        const reader = new FileReader();
        reader.onloadend = () => resolve(reader.result);
        reader.readAsDataURL(blob);
    });
}

Base64 和 Data URL 不是同一个东西。 它们的关系非常密切,但扮演着完全不同的角色。 简单来说:

  • Base64 是一种编码方式(把二进制数据变成文本)。
  • Data URL 是一种 URL 方案(一种特殊格式的网址)。
  • Data URL 经常使用 Base64 编码作为其数据部分。
Data URL
  • 是什么:一种特殊格式的URL(统一资源定位符),它的协议头不是 http: 或 https:,而是 data:。它允许将数据直接内嵌在文档中,而无需从外部服务器加载。
  • 结构:一个完整的Data URL有固定的格式,它包含了元数据和数据本身。 data:[&lt;mediatype&gt;][;base64],&lt;data&gt;
  • data::协议头,表明这是一个Data URL。
  • [&lt;mediatype&gt;]:可选的MIME类型,告诉浏览器数据的格式是什么。例如 image/png, text/css, application/pdf。如果省略,默认为 text/plain。
  • [;base64]:可选的标识符。如果存在,表示后面的 &lt;data&gt; 部分是经过 Base64编码的。如果不存在,则表示数据是URL编码的文本(通常仅适用于纯文本或SVG)。
  • ,:分隔符,前面是元信息,后面是数据本体。
  • &lt;data&gt;:实际的数据内容。如果使用了 ;base64,这里就是Base64编码后的字符串。

例子: 一个PNG图片的Data URL可能长这样: data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==

关系
  • 依赖关系:Data URL 可以使用Base64编码,但不是必须的。对于二进制数据(如图片、PDF),必须使用Base64编码才能放入Data URL。对于纯文本(如CSS或SVG代码),可以不使用Base64,直接写入。
  • 协作方式:Data URL 提供了一个完整的“包装”,告诉浏览器“这里面是什么类型的数据”以及“数据是如何编码的”。而Base64编码则负责完成实际的数据转换工作,将二进制数据变成Data URL能够安全使用的文本形式。
内存管理最佳实践

[补充说明]:

  1. 及时释放 Object URL:
const objectURL = URL.createObjectURL(blob);
// 使用完成后立即释放
URL.revokeObjectURL(objectURL);
  1. 大文件处理:
// 使用切片处理大文件
const chunkSize = 1024 * 1024; // 1MB
let offset = 0;

while (offset < file.size) {
    const chunk = file.slice(offset, offset + chunkSize);
    // 处理分片...
    offset += chunkSize;
}
使用场景对比
技术适用场景优点缺点
Object URL临时显示或下载文件无需服务器,性能好需要手动释放内存
Data URL小文件内联自包含,无需额外请求体积增大33%,不适合大文件
FileReader读取文件内容多种读取方式异步操作,需要回调
MIME

MIME 类型(也称为媒体类型或内容类型)是一种标准,用来表示文档、文件或字节流的性质和格式。 它的全称是 Multipurpose Internet Mail Extensions(多用途互联网邮件扩展)。顾名思义,它最初是为了在电子邮件中支持非 ASCII 字符和附件而设计的,但现在它被广泛应用在互联网的各个领域,尤其是 HTTP 协议中。

MIME 类型就是答案。 服务器在发送数据之前,会在 HTTP 响应的头部(Headers)中包含一个 Content-Type 字段,告诉浏览器(或其他客户端)正在发送的数据是什么类型。

Content-Type: text/html; charset=UTF-8 这行代码告诉浏览器:“接下来要发送的内容是 HTML 文本,使用的字符编码是 UTF-8”。浏览器收到这个信号后,就会启动 HTML 解析器来渲染页面。 如果没有 MIME 类型,浏览器只能靠猜测来处理数据,结果会非常不可靠和安全。

MIME 类型由类型(type) 和子类型(subtype) 两部分组成,中间由一条正斜杠 / 连接。 格式:type/subtype

类型(Type)

代表大的类别,说明数据的一般类型。常见的顶级类型有:

  • text:文本文件,人类可读的文字内容。

    • 例如:text/plain, text/html, text/css
  • image:图像文件或图形数据。

    • 例如:image/jpeg, image/png, image/gif
  • audio:音频或音乐数据。

    • 例如:audio/mpeg, audio/wav
  • video:视频或动态图像数据。

    • 例如:video/mp4, video/webm
  • application:二进制数据或不属于其他类别的数据。通常是需要由应用程序处理的文件。

    • 例如:application/pdf, application/json, application/javascript
  • multipart:由多个部分组成的数据,每个部分可以有自己独立的类型。常用于电子邮件或表单提交。

    • 例如:multipart/form-data
  • font:字体文件。

    • 例如:font/woff2, font/ttf
子类型(Subtype)

代表特定类型中的具体格式。它精确地指定了是哪种 text 或哪种 image。 例如,同是 text 类型,text/plain 是纯文本,而 text/html 是 HTML 文档。

常见 MIME 类型示例
文件格式MIME 类型说明
.htmltext/htmlHTML 文档
.csstext/cssCSS 样式表
.jsapplication/javascriptJavaScript 文件
.jsonapplication/jsonJSON 数据
.pngimage/pngPNG 图像
.jpg / .jpegimage/jpegJPEG 图像
.pdfapplication/pdfAdobe PDF 文档
.zipapplication/zipZIP 压缩档案
.mp3audio/mpegMP3 音频
.mp4video/mp4MP4 视频
重要参数:charset

对于一些文本类型的文件,经常需要指定一个重要的参数——字符编码(character set)。这用 charset 来表示,与主类型用分号 ; 分隔。

  • 示例: text/html; charset=UTF-8
  • 作用: 告诉浏览器应该使用哪种字符编码来解读文本,防止出现乱码。对于现代 Web 开发,最常用的就是 UTF-8。
在实际中的应用
  1. HTTP 协议:如上所述,在 HTTP 请求和响应的头部中,Content-Type 字段至关重要。
  2. 电子邮件:用于标识邮件正文和附件的格式。
  3. 文件系统:某些操作系统会用 MIME 类型来关联打开文件的默认应用程序。
  4. &lt;link&gt; 和 &lt;script&gt; 标签:在 HTML 中,你可以指定引入资源的类型。 <link rel="stylesheet" type="text/css" href="theme.css"> <script type="application/javascript" src="app.js"></script> (注意:在现代 HTML5 中,type 属性对于 CSS 和 JavaScript 通常可以省略,因为它们已是默认值。)

现代JS教程与阮一峰ES6 · 5特殊内建对象/二进制与文件读取/最佳实践

<!DOCTYPE html>
<html lang="zh-CN">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>FileReader文件读取最佳实践</title>
    <style>
        * {
            box-sizing: border-box;
            margin: 0;
            padding: 0;
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
        }

        body {
            background-color: darkorange;
            color: #333;
            min-height: 100vh;
            padding: 30px;
            display: flex;
            flex-direction: column;
            align-items: center;
        }

        .container {
            max-width: 1000px;
            width: 100%;
            background: rgba(255, 255, 255, 0.95);
            border-radius: 15px;
            box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
            padding: 30px;
            margin-top: 20px;
            opacity: 0.95;
        }

        header {
            text-align: center;
            margin-bottom: 15px;
        }

        h1 {
            color: #2c3e50;
            font-size: 2.5rem;
            margin-bottom: 5px;
        }

        .subtitle {
            color: #7f8c8d;
            font-size: 1.2rem;
        }

        .upload-area {
            border: 3px dashed #3498db;
            border-radius: 12px;
            padding: 40px;
            text-align: center;
            margin: 25px 0;
            background: #f8f9fa;
            transition: all 0.3s;
            cursor: pointer;
        }

        .upload-area:hover {
            background: orange;
            border-color: red;
        }

        .upload-icon {
            font-size: 60px;
            color: #3498db;
            margin-bottom: 15px;
        }


        .file-input {
            display: none;
        }

        .btn {
            background: #3498db;
            color: white;
            border: none;
            padding: 12px 25px;
            border-radius: 6px;
            cursor: pointer;
            font-size: 1rem;
            font-weight: bold;
            transition: background 0.3s;
            margin: 10px 5px;
        }

        .btn:hover {
            background: pink;
        }

        .btn-secondary {
            background: #2ecc71;
        }

        .btn-secondary:hover {
            background: #1d9650;
        }

        .results {
            display: grid;
            grid-template-columns: 1fr 1fr;
            gap: 20px;
            margin-top: 30px;
        }

        .result-card {
            background: white;
            border-radius: 10px;
            box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
            padding: 20px;
            overflow: hidden;
        }

        .result-card h3 {
            color: #3498db;
            margin-bottom: 15px;
            padding-bottom: 10px;
            border-bottom: 2px solid #f0f0f0;
        }

        .file-info {
            background: #e8f4fc;
            padding: 15px;
            border-radius: 8px;
            margin-bottom: 15px;
        }

        .preview {
            max-height: 300px;
            overflow: auto;
            padding: 15px;
            background: #f9f9f9;
            border-radius: 8px;
            border: 1px solid #eee;
            white-space: pre-wrap;
            font-family: 'Consolas', monospace;
        }

        .image-preview {
            max-width: 100%;
            max-height: 200px;
            display: block;
            margin: 0 auto;
            border-radius: 6px;
        }

        .progress-bar {
            height: 8px;
            background: #f0f0f0;
            border-radius: 4px;
            margin: 15px 0;
            overflow: hidden;
        }

        .progress {
            height: 100%;
            background: linear-gradient(90deg, #3498db, #2ecc71);
            width: 0%;
            transition: width 0.3s;
        }

        .status {
            text-align: center;
            padding: 10px;
            font-weight: bold;
            color: #7f8c8d;
        }

        .error {
            color: #e74c3c;
            padding: 15px;
            background: #fdecea;
            border-radius: 8px;
            margin: 15px 0;
        }

        @media (max-width: 768px) {
            .results {
                grid-template-columns: 1fr;
            }

            .container {
                padding: 20px;
            }

            h1 {
                font-size: 2rem;
            }
        }
    </style>
</head>

<body>
    <div class="container">
        <header>
            <h1>FileReader文件读取最佳实践</h1>
            <p class="subtitle">使用File API和FileReader读取本地文件内容</p>
        </header>

        <div class="upload-area" id="dropZone">
            <div class="upload-icon">📁</div>
            <h2>拖放文件到此处或点击选择文件</h2>
            <p>支持文本文件、图像、JSON等格式</p>
            <input type="file" id="fileInput" class="file-input" multiple>
            <button class="btn" onclick="document.getElementById('fileInput').click()">选择文件</button>
        </div>

        <div style="text-align: center;">
            <button class="btn btn-secondary" id="readAsTextBtn">读取为文本</button>
            <button class="btn btn-secondary" id="readAsDataURLBtn">读取为DataURL</button>
            <button class="btn btn-secondary" id="readAsArrayBufferBtn">读取为ArrayBuffer</button>
        </div>

        <div class="progress-bar">
            <div class="progress" id="progressBar"></div>
        </div>

        <div class="status" id="status">准备就绪</div>

        <div class="results">
            <div class="result-card">
                <h3>文件信息</h3>
                <div class="file-info" id="fileInfo">
                    <p>尚未选择文件</p>
                </div>
            </div>

            <div class="result-card">
                <h3>文件内容预览</h3>
                <div class="preview" id="filePreview">
                    <p>文件内容将显示在这里</p>
                </div>
            </div>
        </div>

        <div id="errorContainer"></div>
    </div>

    <script>
        // 获取文件的
        const fileInput = document.getElementById('fileInput');
        const dropZone = document.getElementById('dropZone');

        // 文件信息展示
        const fileInfo = document.getElementById('fileInfo');
        const filePreview = document.getElementById('filePreview');
        const progressBar = document.getElementById('progressBar');
        const status = document.getElementById('status');
        const errorContainer = document.getElementById('errorContainer');

        // 三个按钮
        const readAsTextBtn = document.getElementById('readAsTextBtn');
        const readAsDataURLBtn = document.getElementById('readAsDataURLBtn');
        const readAsArrayBufferBtn = document.getElementById('readAsArrayBufferBtn');

        // 当前选中的文件
        let currentFile = null;

        // 拖放功能
        dropZone.addEventListener('dragover', (e) => {
            e.preventDefault();
            dropZone.style.background = 'orange';
            dropZone.style.borderColor = 'red';

        });

        dropZone.addEventListener('dragleave', (e) => {
            e.preventDefault();
            dropZone.style.background = '#f8f9fa';
            dropZone.style.borderColor = '#3498db';
        });

        dropZone.addEventListener('drop', (e) => {
            e.preventDefault();
            dropZone.style.background = '#f8f9fa';
            dropZone.style.borderColor = '#3498db';


            console.log(e.dataTransfer);
            console.log(e);

            if (e.dataTransfer.files.length > 0) {
                handleFileSelection(e.dataTransfer.files[0]);
            }
        });

        // 文件选择处理
        fileInput.addEventListener('change', (e) => {
            if (e.target.files.length > 0) {
                handleFileSelection(e.target.files[0]);
            }
        });

        // 处理文件选择
        function handleFileSelection(file) {
            currentFile = file;
            console.log(file);
            console.log(typeof file);
            console.log(file instanceof File);
            // true 也就是说,在浏览器中读取的文件的类型就是File

            displayFileInfo(file);
            clearError();
            updateStatus('文件已选择,请选择读取方式');

            // 根据文件类型启用适当的按钮
            if (file.type.startsWith('image/')) {
                readAsDataURLBtn.disabled = false;
            } else {
                readAsDataURLBtn.disabled = false;
            }

            readAsTextBtn.disabled = false;
            readAsArrayBufferBtn.disabled = false;
        }

        // 显示文件信息
        function displayFileInfo(file) {
            fileInfo.innerHTML = `
                <p><strong>文件名:</strong> ${file.name}</p>
                <p><strong>类型:</strong> ${file.type || '未知'}</p>
                <p><strong>大小:</strong> ${formatFileSize(file.size)}</p>
                <p><strong>最后修改:</strong> ${new Date(file.lastModified).toLocaleString()}</p>
            `;
        }

        // 格式化文件大小
        function formatFileSize(bytes) {
            if (bytes === 0) return '0 Bytes';
            const k = 1024;
            const sizes = ['Bytes', 'KB', 'MB', 'GB'];
            const i = Math.floor(Math.log(bytes) / Math.log(k));
            return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
        }

        // 读取为文本
        readAsTextBtn.addEventListener('click', () => {
            if (!currentFile) {
                showError('请先选择文件');
                return;
            }

            const reader = new FileReader();

            reader.onloadstart = () => {
                updateStatus('开始读取文件...');
                progressBar.style.width = '30%';
            };

            reader.onprogress = (e) => {
                if (e.lengthComputable) {
                    const percentLoaded = Math.round((e.loaded / e.total) * 100);
                    progressBar.style.width = percentLoaded + '%';
                    updateStatus(`读取中: ${percentLoaded}%`);
                }
            };

            reader.onload = (e) => {
                progressBar.style.width = '100%';
                updateStatus('文件读取完成');

                // 显示文本内容
                filePreview.textContent = e.target.result;

                // 如果是JSON,尝试格式化
                if (currentFile.type === 'application/json' || currentFile.name.endsWith('.json')) {
                    try {
                        const json = JSON.parse(e.target.result);
                        filePreview.textContent = JSON.stringify(json, null, 2);
                    } catch (err) {
                        // 不是有效的JSON,保持原样
                    }
                }
            };

            reader.onerror = () => {
                showError('读取文件时发生错误: ' + reader.error.message);
                progressBar.style.width = '0%';
                updateStatus('读取失败');
            };

            reader.readAsText(currentFile);
        });

        // 读取为DataURL
        readAsDataURLBtn.addEventListener('click', () => {
            if (!currentFile) {
                showError('请先选择文件');
                return;
            }

            const reader = new FileReader();

            reader.onloadstart = () => {
                updateStatus('开始读取文件...');
                progressBar.style.width = '30%';
            };

            reader.onprogress = (e) => {
                if (e.lengthComputable) {
                    const percentLoaded = Math.round((e.loaded / e.total) * 100);
                    progressBar.style.width = percentLoaded + '%';
                    updateStatus(`读取中: ${percentLoaded}%`);
                }
            };

            reader.onload = (e) => {
                progressBar.style.width = '100%';
                updateStatus('文件读取完成');

                // 如果是图片,显示图片预览
                if (currentFile.type.startsWith('image/')) {
                    filePreview.innerHTML = `<img src="${e.target.result}" alt="预览" class="image-preview">`;
                } else {
                    // 否则显示DataURL的前100个字符
                    const dataURL = e.target.result;
                    filePreview.textContent = dataURL.substring(0, 100) + '...';
                }
            };

            reader.onerror = () => {
                showError('读取文件时发生错误: ' + reader.error.message);
                progressBar.style.width = '0%';
                updateStatus('读取失败');
            };

            reader.readAsDataURL(currentFile);
        });

        // 读取为ArrayBuffer
        readAsArrayBufferBtn.addEventListener('click', () => {
            if (!currentFile) {
                showError('请先选择文件');
                return;
            }

            const reader = new FileReader();

            reader.onloadstart = () => {
                updateStatus('开始读取文件...');
                progressBar.style.width = '30%';
            };

            reader.onprogress = (e) => {
                if (e.lengthComputable) {
                    const percentLoaded = Math.round((e.loaded / e.total) * 100);
                    progressBar.style.width = percentLoaded + '%';
                    updateStatus(`读取中: ${percentLoaded}%`);
                }
            };

            reader.onload = (e) => {
                progressBar.style.width = '100%';
                updateStatus('文件读取完成');

                // 显示ArrayBuffer信息
                const arrayBuffer = e.target.result;
                filePreview.innerHTML = `
                    <p><strong>ArrayBuffer大小:</strong> ${arrayBuffer.byteLength} 字节</p>
                    <p><strong>前16字节:</strong> ${getFirstBytes(arrayBuffer, 16)}</p>
                `;
            };

            reader.onerror = () => {
                showError('读取文件时发生错误: ' + reader.error.message);
                progressBar.style.width = '0%';
                updateStatus('读取失败');
            };

            reader.readAsArrayBuffer(currentFile);
        });

        // 获取ArrayBuffer的前几个字节
        function getFirstBytes(arrayBuffer, count) {
            const uint8Array = new Uint8Array(arrayBuffer);
            const bytes = [];
            for (let i = 0; i < Math.min(count, uint8Array.length); i++) {
                bytes.push(uint8Array[i].toString(16).padStart(2, '0'));
            }
            return bytes.join(' ');
        }

        // 更新状态
        function updateStatus(message) {
            status.textContent = message;
        }

        // 显示错误
        function showError(message) {
            errorContainer.innerHTML = `<div class="error">${message}</div>`;
        }

        // 清除错误
        function clearError() {
            errorContainer.innerHTML = '';
        }

        // 初始化按钮状态
        readAsTextBtn.disabled = true;
        readAsDataURLBtn.disabled = true;
        readAsArrayBufferBtn.disabled = true;
    </script>
</body>

</html>

廖雪峰教程 · 7浏览器/7.3 文件

文件

在HTML表单中,可以上传文件的唯一控件就是&lt;input type="file"&gt;。

注意:当一个表单包含&lt;input type="file"&gt;时,表单的enctype必须指定为multipart/form-data,method必须指定为post,浏览器才能正确编码并以multipart/form-data格式发送表单的数据。

出于安全考虑,浏览器只允许用户点击&lt;input type="file"&gt;来选择本地文件,用JavaScript对&lt;input type="file"&gt;的value赋值是没有任何效果的。当用户选择了上传某个文件后,JavaScript也无法获得该文件的真实路径:

待上传文件:

通常,上传的文件都由后台服务器处理,JavaScript可以在提交表单时对文件扩展名做检查,以便防止用户上传无效格式的文件:

let f = document.getElementById('test-file-upload');
let filename = f.value; // 'C:\fakepath\test.png'
if (!filename || !(filename.endsWith('.jpg') || filename.endsWith('.png') || filename.endsWith('.gif'))) {
    alert('Can only upload image file.');
    return false;
}
File API

由于JavaScript对用户上传的文件操作非常有限,尤其是无法读取文件内容,使得很多需要操作文件的网页不得不用Flash这样的第三方插件来实现。

随着HTML5的普及,新增的File API允许JavaScript读取文件内容,获得更多的文件信息。

HTML5的File API提供了File和FileReader两个主要对象,可以获得文件信息并读取文件。

下面的例子演示了如何读取用户选取的图片文件,并在一个&lt;div&gt;中预览图像:

图片预览:

let
    fileInput = document.getElementById('test-image-file'),
    info = document.getElementById('test-file-info'),
    preview = document.getElementById('test-image-preview');
// 监听change事件:
fileInput.addEventListener('change', function () {
    // 清除背景图片:
    preview.style.backgroundImage = '';
    // 检查文件是否选择:
    if (!fileInput.value) {
        info.innerHTML = '没有选择文件';
        return;
    }
    // 获取File引用:
    let file = fileInput.files[0];
    // 获取File信息:
    info.innerHTML = '文件: ' + file.name + '<br>' +
                     '大小: ' + file.size + '<br>' +
                     '修改: ' + file.lastModified;
    if (file.type !== 'image/jpeg' && file.type !== 'image/png' && file.type !== 'image/gif') {
        alert('不是有效的图片文件!');
        return;
    }
    // 读取文件:
    let reader = new FileReader();
    reader.onload = function(e) {
        let
            data = e.target.result; // 'data:image/jpeg;base64,/9j/4AAQSk...(base64编码)...'
        preview.style.backgroundImage = 'url(' + data + ')';
    };
    // 以DataURL的形式读取文件:
    reader.readAsDataURL(file);
});

上面的代码演示了如何通过HTML5的File API读取文件内容。以DataURL的形式读取到的文件是一个字符串,类似于data:image/jpeg;base64,/9j/4AAQSk...(base64编码)...,常用于设置图像。如果需要服务器端处理,把字符串base64,后面的字符发送给服务器并用Base64解码就可以得到原始文件的二进制内容。

回调

上面的代码还演示了JavaScript的一个重要的特性就是单线程执行模式。在JavaScript中,浏览器的JavaScript执行引擎在执行JavaScript代码时,总是以单线程模式执行,也就是说,任何时候,JavaScript代码都不可能同时有多于1个线程在执行。

你可能会问,单线程模式执行的JavaScript,如何处理多任务?

在JavaScript中,执行多任务实际上都是异步调用,比如上面的代码:

reader.readAsDataURL(file);

就会发起一个异步操作来读取文件内容。因为是异步操作,所以我们在JavaScript代码中就不知道什么时候操作结束,因此需要先设置一个回调函数:

reader.onload = function(e) {
    // 当文件读取完成后,自动调用此函数:
};

当文件读取完成后,JavaScript引擎将自动调用我们设置的回调函数。执行回调函数时,文件已经读取完毕,所以我们可以在回调函数内部安全地获得文件内容。